use std::collections::HashSet;

use regex::Regex;
use syn::{parse::{Parse, ParseStream}, Ident, LitStr, Result, Token};

pub struct RouteAttr {
    pub struct_name: Option<Ident>,

    pub path: Vec<PathSegment>,
    
    pub path_param: Vec<Ident>,
    pub query_param: Vec<Ident>,              
    pub body_param: Option<Ident>,
    pub state_param: Option<Ident>,
}

impl RouteAttr {
    pub fn is_body_param(&self, param_name: &Ident) -> bool {
        self.body_param.as_ref().map_or(false, |body_name| param_name == body_name)
    }

    pub fn is_state_param(&self, param_name: &Ident) -> bool {
        self.state_param.as_ref().map_or(false, |body_name| param_name == body_name)
    }

    pub fn is_query_param(&self, param_name: &Ident) -> bool {
        self.query_param.iter().any(|query_name| query_name == param_name)
    }

    pub fn is_path_param(&self, param_name: &Ident) -> bool {
        self.path_param.iter().any(|query_name| query_name == param_name)
    }
}

#[derive(Debug)]
pub enum PathSegment {
    Static(String),
    Param(Ident),
}

impl Parse for RouteAttr {
    fn parse(input: ParseStream) -> Result<Self> {
        let mut struct_name: Option<Ident> = None;
        let mut path: Option<Vec<PathSegment>> = None;

        let mut query_param: Option<Vec<Ident>> = None;
        let mut body_param: Option<Ident> = None;
        let mut state_param: Option<Ident> = None;

        while !input.is_empty() {
            // Parse key and skip '='
            let key: Ident = input.parse()?;
            input.parse::<Token![=]>()?;

            match key.to_string().as_str() {
                "struct_name" => {
                    if struct_name.is_some() {
                        return Err(syn::Error::new(key.span(), "duplicate struct_name"));
                    }
                    struct_name = Some(input.parse()?);
                }
                "path" => {
                    if path.is_some() {
                        return Err(syn::Error::new(key.span(), "duplicate path"));
                    }
                    let path_lit = input.parse()?;
                    path = Some(parse_path_segments(&path_lit)?);
                }
                "query" => {
                    if query_param.is_some() {
                        return Err(syn::Error::new(key.span(), "duplicate query"));
                    }
                    let query_lit = input.parse()?;
                    query_param = Some(parse_query_idents(&query_lit)?);
                }
                "body" => {
                    if body_param.is_some() {
                        return Err(syn::Error::new(key.span(), "duplicate body"));
                    }
                    body_param = Some(input.parse()?);
                }
                "state" => {
                    if state_param.is_some() {
                        return Err(syn::Error::new(key.span(), "duplicate body"));
                    }
                    state_param = Some(input.parse()?);
                }
                other => {
                    return Err(syn::Error::new(key.span(), format!("unknown attribute key `{}`", other)));
                }
            }

            if input.peek(Token![,]) {
                input.parse::<Token![,]>()?;
            }
        }

        let path = path.ok_or_else(|| syn::Error::new(input.span(), "missing path"))?;
        let query_param = query_param.unwrap_or_default();
        let path_param = extract_path_param_names(&path);

        Ok(RouteAttr {
            struct_name,
            path,
            path_param,
            query_param,
            state_param,
            body_param,
        })
    }
}

fn extract_path_param_names(path: &[PathSegment]) -> Vec<Ident> {
    path.iter()
        .filter_map(|seg| {
            match seg {
                PathSegment::Param(name) => Some(name.clone()),
                PathSegment::Static(_) => None 
            }
        })
        .collect()
}

fn parse_path_segments(path_lit: &LitStr) -> Result<Vec<PathSegment>> {
    validate_path_format(path_lit)?;

    let path = path_lit.value();
    let segments = path
        .trim_start_matches('/')
        .split('/')
        .map(|seg| {
            if let Some(caps) = Regex::new(r"^<([a-zA-Z_][a-zA-Z0-9_]*)>$").unwrap().captures(seg) {
                let name = &caps[1];
                Ok(PathSegment::Param(syn::parse_str(name)?))
            } else {
                Ok(PathSegment::Static(seg.to_string()))
            }
        })
        .collect::<Result<Vec<_>>>()?;

    Ok(segments)
}

fn parse_query_idents(query_lit: &LitStr) -> Result<Vec<Ident>> {
    validate_query_format(query_lit)?;

    query_lit
        .value()
        .split('&')
        .filter(|s| !s.trim().is_empty())
        .map(|name| syn::parse_str::<Ident>(name))
        .collect()
}

fn validate_path_format(path_lit: &LitStr) -> Result<()> {
    let path = path_lit.value();

    if !path.starts_with('/') {
        return Err(syn::Error::new_spanned(path_lit, "Path must start with `/`"));
    }

    let re = Regex::new(r"^(/[a-zA-Z0-9_-]+|/<[a-zA-Z_][a-zA-Z0-9_]*>)+$").unwrap();
    if !re.is_match(&path) {
        return Err(syn::Error::new_spanned(
            path_lit,
            "Invalid path format. Expected format: `/fixed/<param>/<id>`",
        ));
    }

    let param_re = Regex::new(r"<([a-zA-Z_][a-zA-Z0-9_]*)>").unwrap();
    let mut seen = HashSet::new();
    for cap in param_re.captures_iter(&path) {
        if let Some(name) = cap.get(1) {
            let param = name.as_str();
            if !seen.insert(param.to_string()) {
                return Err(syn::Error::new_spanned(
                    path_lit,
                    format!("Duplicate path parameter `<{}>`", param),
                ));
            }
        }
    }

    Ok(())
}

fn validate_query_format(query_lit: &LitStr) -> Result<()> {
    let query = query_lit.value();

    if query.trim().is_empty() {
        return Ok(());
    }

    let ident_re = Regex::new(r"^[a-zA-Z_][a-zA-Z0-9_]*$").unwrap();
    let mut seen = HashSet::new();

    for segment in query.split('&') {
        if segment.is_empty() {
            return Err(syn::Error::new_spanned(
                query_lit,
                "Empty field found in query string (e.g., `&&`, `&foo&`)",
            ));
        }

        if !ident_re.is_match(segment) {
            return Err(syn::Error::new_spanned(
                query_lit,
                format!("Invalid query field name: `{}`. Must be a valid identifier", segment),
            ));
        }

        if !seen.insert(segment) {
            return Err(syn::Error::new_spanned(
                query_lit,
                format!("Duplicate query parameter: `{}`", segment),
            ));
        }
    }

    Ok(())
}